﻿using Castle.Core.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Yto.Common.Extensions;

namespace Yto.Reflection
{
    public class TypeFinder : ITypeFinder
    {
        public ILogger Logger { get; set; }
        private readonly Object _syncObj = new Object();
        private Type[] _types;
        private readonly IAssemblyFinder _assemblyFinder;

        public TypeFinder(IAssemblyFinder assemblyFinder)
        {
            Logger = NullLogger.Instance;
            _assemblyFinder = assemblyFinder;
        }

        public Type[] FinAll()
        {
            return GetAllTypes().ToArray();
        }

        public Type[] FindByCondition(Func<Type, bool> predicate)
        {
            return GetAllTypes().Where(predicate).Distinct().ToArray();
        }

        private Type[] GetAllTypes()
        {
            if (_types == null)
            {
                lock (_syncObj)
                {
                    if(_types == null)
                    {
                        _types = GetTypes().ToArray();
                    }
                }
            }
            return _types;
        }

        private List<Type> GetTypes()
        {
            var types = new List<Type>();
            var assemblies = _assemblyFinder.GetAssemblies().Distinct();

            foreach(var item in assemblies)
            {
                try
                {
                    Type[] tempTypes;
                    try
                    {
                        tempTypes = item.GetTypes();
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        tempTypes = ex.Types;
                    }

                    if (tempTypes.IsNullOrEmpty())
                    {
                        continue;
                    }
                    types.AddRange(tempTypes.Where(t => t != null));
                }
                catch (Exception ex)
                {
                    Logger.Warn(ex.Message, ex);   
                }
            }

            return types;
        }
    }
}
